package com.cherrish.demo.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * @author cherrish
 * @time 2019-03-01 10:27
 * @name EchoServer
 * @desc:
 */
public class EchoServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        EchoServer server = new EchoServer();
        server.startAioServer();
    }

    public void startAioServer() throws IOException, InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
        server.bind(new InetSocketAddress(9090));
        server.accept(server, new AcceptCompletionHandler());
        latch.await();
    }

    static class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel>{

        @Override
        public void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel attachment) {
            attachment.accept(attachment, this);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            result.read(buffer, buffer, new ReadCompletionHandler(result));
        }

        @Override
        public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) {
            exc.printStackTrace();
        }
    }

    static class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer>{

        private AsynchronousSocketChannel channel;

        public ReadCompletionHandler(AsynchronousSocketChannel channel) {
            this.channel = channel;
        }

        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            attachment.flip();
            byte[] body = new byte[attachment.remaining()];
            attachment.get(body);
            System.out.println("Server receives message: ");
            System.out.println(new String(body, UTF_8));
            doWrite();
        }

        private void doWrite(){
            ByteBuffer buffer = ByteBuffer.wrap(("Hello, I'm server. It's " + new Date()).getBytes());
            channel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result, ByteBuffer attachment) {
                    if(attachment.hasRemaining()){
                        channel.write(attachment, attachment, this);
                    }
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void startNioServer() throws IOException {
        ServerSocketChannel server = ServerSocketChannel.open();
        server.configureBlocking(false);
        server.bind(new InetSocketAddress(9090));

        Selector selector = Selector.open();
        server.register(selector, SelectionKey.OP_ACCEPT);

        while (true){
            int size = selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                if(key.isAcceptable()){
                    accept(key, selector);
                }else if(key.isReadable()){
                    read(key, selector);
                }else if(key.isWritable()){
                    write(key);
                }
            }
        }
    }

    private void accept(SelectionKey key, Selector selector) throws IOException {
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        SocketChannel channel = server.accept();
        channel.configureBlocking(false);
        channel.register(selector, SelectionKey.OP_READ);
    }

    private void read(SelectionKey key, Selector selector) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        handle(buffer);
        channel.register(selector, SelectionKey.OP_WRITE);
    }

    private void write(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        response(channel);
    }

    /*声明一个线程池，用于异步处理客户端请求*/
    private static final ExecutorService executor = Executors.newFixedThreadPool(10);
    public void startServer1() throws IOException {
        ServerSocketChannel server = ServerSocketChannel.open();
        server.bind(new InetSocketAddress(9090));
        while (true){
            SocketChannel channel = server.accept();
            executor.execute(() -> {
                try {
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    channel.read(buffer);
                    handle(buffer);
                    response(channel);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }

    public void startServer() throws IOException {
        ServerSocketChannel server = ServerSocketChannel.open();
        server.bind(new InetSocketAddress(9090));
        while (true){
            SocketChannel channel = server.accept();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            channel.read(buffer);
            handle(buffer);
            response(channel);
        }
    }
    private void handle(ByteBuffer buffer){
        buffer.flip();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        System.out.println("Servcer receives message: ");
        System.out.println(new String(bytes, UTF_8));
    }
    private void response(SocketChannel channel) throws IOException {
        ByteBuffer buffer = ByteBuffer.wrap(("Hello, I'm Server. It's " + new Date()).getBytes());
        channel.write(buffer);
        channel.close();
    }
}
